''' <summary>
''' Class for all classes to read WDM data
''' </summary>
''' <remarks>
''' atcWDM library should be able to read/write WDM files, however there are problems in the Fortran
''' libaries that are called that causes terrible crashes; therefore, for reading, use the fully managed
''' atcWDMVb library which does not cause any errors. Writing will still be problematic, however see
''' Mark Gray's email of 10/3/11 for hints about how to work around problems.
''' 
''' Starting in October 2011, this library was changed so that all datasets are returned (no separate tables)
''' Scenario, if present, is prepended to PCode
''' </remarks>
Public Class clsDataWDM
    Inherits clsData

    Private mNumRecords As Integer
    Private mDatasetNum As Integer = 0
    Private mRecordNumDS As Integer = -1
    'Private mWDMRead As atcWDM.atcDataSourceWDM
    Private mWDMRead As atcWdmVb.atcWDMfile

    ''' <summary>
    ''' Instantiate clsData, assign filename, open file, set filelength and fieldnames
    ''' </summary>
    ''' <param name="_Filename">Name of text file to read</param>
    Public Overrides Sub Initialize(ByVal _Filename As String)
        MyBase.Initialize(_Filename)
        mDataType = enumDataType.WDM
        mFieldNames.Clear()
        mTableNames.Clear()
        mTableNames.Add(IO.Path.GetFileName(_Filename))
        mStationIDs.Clear()
        mPCodes.Clear()
        mMinDate = Date.MaxValue
        mMaxDate = Date.MinValue
        mNumRecords = 0

        'mWDMRead = New atcWDM.atcDataSourceWDM
        mWDMRead = New atcWdmVb.atcWDMfile
        'If Not mWDMRead.Open(mFilename) Then Throw New IO.IOException("Error opening " & mFilename) : Exit Sub
        mWDMRead.Open(mFilename)

        mFieldNames.AddRange(New String() {"Station_ID", "PCode", "Date_Time", "Result"})
        For i As Integer = 0 To mWDMRead.DataSets.Count - 1
            Dim ts As atcData.atcTimeseries = mWDMRead.DataSets(i)
            With ts
                mNumRecords += .numValues
                If Not mStationIDs.Contains(StationID_DS(i)) Then mStationIDs.Add(StationID_DS(i))
                If Not mPCodes.Contains(PCode_DS(i)) Then mPCodes.Add(PCode_DS(i))
                Dim dt1 As Date = Date.FromOADate(.Dates.Value(0))
                Dim dt2 As Date = Date.FromOADate(.Dates.Value(.numValues - 1))
                If dt1 < mMinDate Then mMinDate = dt1
                If dt2 > mMaxDate Then mMaxDate = dt2
                'mTableNames.Add(String.Format("{0} {1} at {2}", Proper(.GetValue("Scenario")), Proper(.GetValue("Constituent")), Proper(.GetValue("Location"))))
            End With
        Next
        mDatasetNum = 0
    End Sub

    ''' <summary>
    ''' Instantiate clsData, assign filename, open file, set filelength and fieldnames
    ''' </summary>
    ''' <param name="_Filename">Name of text file to read</param>
    Public Sub InitializeWrite(ByVal _Filename As String)
        MyBase.Initialize(_Filename)
        mDataType = enumDataType.WDM
    End Sub

    Public Sub Write(ByVal Scenario As String, ByVal StationID As String, ByVal StationName As String, ByVal PCode As String, ByVal DateTimes() As DateTime, ByVal Values() As Double)
        Try
            Dim lTS As New atcData.atcTimeseries(Nothing)
            lTS.Dates = New atcData.atcTimeseries(Nothing)
            lTS.numValues = Values.Length
            Dim tu As atcUtility.atcTimeUnit = atcUtility.atcTimeUnit.TUHour
            With lTS.Attributes
                .SetValue("Scenario", Scenario)
                .SetValue("Location", StationID)
                .SetValue("Description", StationName)
                .SetValue("Constituent", PCode)
                .SetValue("TSType", PCode) 'needed by some legacy apps
                .SetValue("Time Step", 1)
                If DateTimes.Length > 1 Then
                    Dim lDateSpan As TimeSpan = DateTimes(1).Subtract(DateTimes(0))
                    If lDateSpan.TotalSeconds = 1 Then
                        tu = atcUtility.atcTimeUnit.TUSecond
                    ElseIf lDateSpan.TotalMinutes = 1 Then
                        tu = atcUtility.atcTimeUnit.TUMinute
                    ElseIf lDateSpan.TotalHours = 1 Then
                        tu = atcUtility.atcTimeUnit.TUHour
                    ElseIf lDateSpan.TotalDays = 1 Then
                        tu = atcUtility.atcTimeUnit.TUDay
                    ElseIf lDateSpan.TotalDays = 30 Then
                        tu = atcUtility.atcTimeUnit.TUMonth
                    ElseIf lDateSpan.TotalDays = 365 Then
                        tu = atcUtility.atcTimeUnit.TUYear
                    ElseIf lDateSpan.TotalDays = 3650 Then
                        tu = atcUtility.atcTimeUnit.TUCentury
                    Else
                        tu = atcUtility.atcTimeUnit.TUUnknown
                        Throw New InvalidConstraintException(String.Format("WDM files are generally expected to contain constant increment time series; specifically, the first two observations must have a standard time step (second, hour, day, month, or year); when this is not true, the dataset cannot be saved to the WDM file. Please examine your WRDB data for Station ID='{0}' and PCode='{1}'", StationID, PCode))
                    End If
                End If
                .SetValue("tu", tu)
                .SetValue("SJDay", DateTimes(0).ToOADate)
                .SetValue("EJDay", DateTimes(lTS.numValues - 1).ToOADate)
            End With
            lTS.Value(0) = atcUtility.GetNaN()
            For i As Integer = 1 To lTS.numValues
                'there is an apparent problem when subsequent identical dates are used; increment slightly
                If i > 1 AndAlso DateTimes(i - 1) <= DateTimes(i - 2) Then DateTimes(i - 1) = DateTimes(i - 2).AddMinutes(1)
                lTS.Dates.Value(i - 1) = DateTimes(i - 1).ToOADate
                lTS.Value(i) = Values(i - 1)
            Next
            lTS.Dates.Value(lTS.numValues) = atcUtility.TimAddJ(lTS.Dates.Value(lTS.numValues - 1), tu, 1, 1)

            Dim Wdm As New atcWDM.atcDataSourceWDM

            'for some reason, the first time a file is opened during a session, an error occurs
            Try
                Wdm.Open(mFilename)
            Catch ex As Exception
            End Try

            If Wdm.Open(mFilename) Then
                For Each ds As atcData.atcDataSet In Wdm.DataSets
                    If ds.Attributes.GetValue("Scenario", "") = Scenario And ds.Attributes.GetValue("Location", "") = StationID And ds.Attributes.GetValue("Constituent", "") = PCode Then
                        Wdm.RemoveDataset(ds)
                        Exit For
                    End If
                Next

                Dim dsn As Integer = 0
                dsn += 1
                lTS.Attributes.SetValue("ID", dsn)
                If Not Wdm.AddDataSet(lTS, atcData.atcDataSource.EnumExistAction.ExistRenumber) Then Throw New InvalidOperationException(String.Format("Unable to write WDM file: {0} while writing Station ID='{1}' and PCode='{2}'", mFilename.Replace("\", "\\"), StationID, PCode))
            Else
                Throw New System.IO.IOException(String.Format("Unable to open WDM file: {0} while writing Station ID='{1}' and PCode='{2}'", mFilename.Replace("\", "\\"), StationID, PCode))
            End If
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Public Overrides Sub Rewind()
        MyBase.Rewind()
        mDatasetNum = 0
        mRecordNumDS = -1
    End Sub

    ''' <summary>
    ''' Currently active station ID (may allow some sources to avoid long sequential reads)
    ''' </summary>
    Public Overrides Property ActiveStationID() As String
        Get
            Return MyBase.ActiveStationID
        End Get
        Set(ByVal value As String)
            MyBase.ActiveStationID = value
            For i As Integer = 0 To mWDMRead.DataSets.Count - 1
                If StationID_DS(i) = value Then
                    mDatasetNum = i
                    mRecordNumDS = -1
                    Exit For
                End If
            Next
        End Set
    End Property

    ''' <summary>
    ''' Read next line from file into memory, storing into mItems array
    ''' Also save mLastLineRead for error reporting
    ''' Automatically advance through all datasets
    ''' </summary>
    ''' <returns>True if successful, false if eof reached and no more data</returns>
    Public Overrides Function Read() As Boolean
        Try
            If Not MyBase.Read() Then Return False
            If mRecordNum > mNumRecords - 1 Then Return False
            mItems.Clear()
            Dim ts As atcData.atcTimeseries = mWDMRead.DataSets(mDatasetNum)
            mRecordNumDS += 1
            If mRecordNumDS > ts.numValues - 1 Then
                mDatasetNum += 1
                If mActiveStationIDSet Then                    'search for next dataset having this station id
                    While mDatasetNum <= mWDMRead.DataSets.Count - 1 AndAlso StationID_DS(mDatasetNum) <> mActiveStationID
                        mDatasetNum += 1
                    End While
                End If

                If mDatasetNum > mWDMRead.DataSets.Count - 1 Then Return False
                mRecordNumDS = 0
                ts = mWDMRead.DataSets(mDatasetNum)
            End If

            mItems.Add(StationID_DS(mDatasetNum))
            mItems.Add(PCode_DS(mDatasetNum))
            mItems.Add(RoundDateTime(Date.FromOADate(ts.Dates.Value(mRecordNumDS))).ToString(MyDateTimeFormat))
            Dim v As Double = ts.Value(mRecordNumDS + 1) 'per email from Mark 12/6/07 need to use next index
            If (Double.IsNaN(v) Or Double.IsInfinity(v)) Then mItems.Add("") Else mItems.Add(Math.Round(v, 5))
            Return True
        Catch ex As Exception
            ErrorMsg(, ex)
            Return False
        End Try
    End Function

    Private Function StationID_DS(DatasetNum As Integer) As String
        With mWDMRead.DataSets(DatasetNum).Attributes
            Dim StaID As String = .GetValue("Location")
            If StaID <> "" Then StaID &= " "
            Dim DsnID As String = .GetValue("ID")
            StaID &= String.Format("(DSN {0})", DsnID)
            Return StaID
        End With
    End Function

    Private Function PCode_DS(DatasetNum As Integer) As String
        With mWDMRead.DataSets(DatasetNum).Attributes
            Dim Scenario As String = Proper(.GetValue("Scenario")).Trim
            If Scenario <> "" Then Scenario &= " "
            Return Scenario & .GetValue("Constituent")
        End With
    End Function

    ''' <summary>
    ''' Get data table containing time series data for a specified station id and pcode
    ''' Table will contain date-time field and result field
    ''' </summary>
    Public Overrides Function GetDataTable(ByVal StationID As String, ByVal PCode As String) As DataTable
        If Not mStationIDs.Contains(StationID) Then
            Throw New ApplicationException("WDM file does not contain the station: " & StationID)
            Return Nothing
        End If
        Dim itemNum As Integer = -1
        If Not mPCodes.Contains(PCode) Then
            Throw New ApplicationException("WDM file does not contain the column: " & PCode)
            Return Nothing
        End If
        Dim dt As DataTable = MyBase.SetupDataTable(enumDataTableColumns.Date_Result)
        'find the dataset that references that particular station & pcode
        For i As Integer = 0 To mWDMRead.DataSets.Count - 1
            If StationID_DS(i) = StationID AndAlso PCode_DS(i) = PCode Then
                Dim ts As atcData.atcTimeseries = mWDMRead.DataSets(i)
                For j As Integer = 0 To ts.numValues - 1
                    Dim d As Date = Date.FromOADate(ts.Dates.Value(j))
                    Dim v As Double = ts.Value(j + 1) 'per email from Mark 12/6/07 need to use next index
                    If Not (Double.IsNaN(v) Or Double.IsInfinity(v)) Then dt.Rows.Add(d, Math.Round(v, 5))
                Next
                Exit For
            End If
        Next
        Return dt
    End Function

    ''' <summary>
    ''' Determine percent of file that has been read
    ''' </summary>
    ''' <returns>Percent of file read</returns>
    Public Overrides Function PercentComplete() As Double
        Return (mRecordNum + 1) * 100.0 / mNumRecords
    End Function

    Private Function Proper(ByVal s As String) As String
        Return My.Application.Culture.TextInfo.ToTitleCase(s)
    End Function

End Class
